﻿using System;
using System.Drawing;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using static MatrixEditor.Global;

namespace MatrixEditor
{
	internal class DataFile
	{
		private static readonly string HEADER_MAGIC = "MTIX";
		private static readonly int HEADER_LENGTH   = Marshal.SizeOf(typeof(MatrixHeader));

		[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
		private struct MatrixHeader
		{
			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
			public string magic;	    // 5
			public int    file_size;    // 4
			public byte   color_count;  // 1 0~255   colors
			public int    color_size;   // 4
			public byte   single_color; // 1 0/1
			public byte   frame_count;  // 1 0~255   frames
			public ushort frame_size;   // 2 0~65535 pixels
			public ushort frame_peroid; // 2
			public int    reserved;     // 4, total: 24
		}

		public enum DATAFILE_ERROR
		{
			SUCCESS,
			INVALID_FILE,
			INVALID_DATA,
			UNKNOWN,
		}


		#region 数据导入导出函数
		/// <summary>
		/// 导出用户数据到指定文件
		/// </summary>
		/// <param name="filename">输出文件名</param>
		/// <returns></returns>
		public static DATAFILE_ERROR Export(string filename)
		{
			var result  = DATAFILE_ERROR.SUCCESS;
			var header = new MatrixHeader
			{
				magic        = HEADER_MAGIC,
				frame_count  = (byte)Frames.Count,
				frame_size   = (ushort)(Marshal.SizeOf((byte)Frames[0][0]) * Frames[0].Length),
				frame_peroid = (ushort)Period,
				color_count  = (byte)(Colors.Length / 3),
				single_color = (byte)(SingleColor ? 1 : 0),
				color_size   = Marshal.SizeOf(
					Color.FromArgb(
						Colors[0, 0],
						Colors[0, 1],
						Colors[0, 2]
					).ToArgb()
				),
			};

			byte[] frame_data = CompressData();

			header.file_size = HEADER_LENGTH +
							   header.color_count * header.color_size +
							   frame_data.Length;

			using (var stream = new FileStream(filename, FileMode.Create, FileAccess.Write))
			{
				using (var writer = new BinaryWriter(stream))
				{
					try
					{
						// 写入文件头
						writer.Write(StructToBytes(header));

						// 写入色盘数据
						for (int index = 0; index < header.color_count; index++)
						{
							writer.Write(BitConverter.GetBytes(
								Color.FromArgb(
									Colors[index, 0],
									Colors[index, 1],
									Colors[index, 2]
								).ToArgb())
							);
						}

						// 写入帧数据
						writer.Write(frame_data);
					}
					catch (Exception ex)
					{
						MessageBox.Show(ex.ToString());
						result = DATAFILE_ERROR.UNKNOWN;
					}
				}
			}

			return result;
		}

		/// <summary>
		/// 从指定文件导入用户数据
		/// </summary>
		/// <param name="filename">输入文件名</param>
		/// <returns></returns>
		public static DATAFILE_ERROR Import(string filename)
		{
			var result = DATAFILE_ERROR.SUCCESS;

			using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read))
			{
				using (var reader = new BinaryReader(stream))
				{
					byte[] bytes = reader.ReadBytes(HEADER_LENGTH);

					// 读取文件头
					var header = (MatrixHeader)BytesToStuct(bytes, typeof(MatrixHeader));

					Period      = header.frame_peroid;
					SingleColor = header.single_color == 1;

					try
					{
						if (!string.Equals(header.magic, HEADER_MAGIC))
						{
							result = DATAFILE_ERROR.INVALID_FILE;
						}
						else
						{
							// 读取并覆盖色盘数据
							for (int index = 0; index < header.color_count; index++)
							{
								bytes = reader.ReadBytes(header.color_size);

								if (BitConverter.IsLittleEndian) { Array.Reverse(bytes); }

								// bytes[0] = alpha, passed
								Colors[index, 0] = bytes[1];
								Colors[index, 1] = bytes[2];
								Colors[index, 2] = bytes[3];
							}

							Frames.Clear();

							// 读取并覆盖帧数据
							bytes = DecompressData(reader.ReadBytes(header.file_size));

							if (bytes is null) { return DATAFILE_ERROR.INVALID_DATA; }

							for (int index = 0; index < header.frame_count; index++)
							{
								int[] frame = new int[header.frame_size];

								Array.Copy(bytes, index * header.frame_size, frame, 0, header.frame_size);

								Frames.Add(frame);
							}
						}
					}
					catch (Exception ex)
					{
						MessageBox.Show(ex.ToString());
						result = DATAFILE_ERROR.UNKNOWN;
					}
				}
			}

			return result;
		}

		public static void ImportFromResources(byte[] data)
		{
			using (var stream = new MemoryStream(data))
			{
				using (var reader = new BinaryReader(stream))
				{
					byte[] bytes = reader.ReadBytes(HEADER_LENGTH);

					// 读取文件头
					var header = (MatrixHeader)BytesToStuct(bytes, typeof(MatrixHeader));

					Period      = header.frame_peroid;
					SingleColor = header.single_color == 1;

					// 读取并覆盖色盘数据
					for (int index = 0; index < header.color_count; index++)
					{
						bytes = reader.ReadBytes(header.color_size);

						if (BitConverter.IsLittleEndian) { Array.Reverse(bytes); }

						// bytes[0] = alpha, passed
						Colors[index, 0] = bytes[1];
						Colors[index, 1] = bytes[2];
						Colors[index, 2] = bytes[3];
					}

					Frames.Clear();

					// 读取并覆盖帧数据
					bytes = DecompressData(reader.ReadBytes(header.file_size));

					for (int index = 0; index < header.frame_count; index++)
					{
						int[] frame = new int[header.frame_size];

						Array.Copy(bytes, index * header.frame_size, frame, 0, header.frame_size);

						Frames.Add(frame);
					}
				}
			}
		}
		#endregion


		#region 字节转换相关函数
		/// <summary>
		/// 结构体转 byte 数组
		/// </summary>
		/// <param name="obj">要转换的结构体</param>
		/// <returns>转换后的 byte 数组</returns>
		private static byte[] StructToBytes(object obj)
		{
			int size = Marshal.SizeOf(obj);
			byte[] bytes = new byte[size];
			IntPtr ptr = Marshal.AllocHGlobal(size);
			Marshal.StructureToPtr(obj, ptr, false);
			Marshal.Copy(ptr, bytes, 0, size);
			Marshal.FreeHGlobal(ptr);

			return bytes;
		}

		/// <summary>
		/// byte数组转结构体
		/// </summary>
		/// <param name="bytes">byte数组</param>
		/// <param name="type">结构体类型</param>
		/// <returns>转换后的结构体</returns>
		private static object BytesToStuct(byte[] bytes, Type type)
		{
			//得到结构体的大小
			int size = Marshal.SizeOf(type);

			if (size > bytes.Length) { return null; }

			IntPtr ptr = Marshal.AllocHGlobal(size);
			Marshal.Copy(bytes, 0, ptr, size);
			object obj = Marshal.PtrToStructure(ptr, type);
			Marshal.FreeHGlobal(ptr);
			
			return obj;
		}

		private static byte[] CompressData()
		{
			using (MemoryStream memory_stream = new MemoryStream())
			{
				using (DeflateStream deflate_stream = new DeflateStream(memory_stream, CompressionMode.Compress, true))
				{
					foreach (var frame in Frames)
					{
						foreach (var pixel in frame)
						{
							deflate_stream.WriteByte(Convert.ToByte(pixel));
						}
					}
				}

				return memory_stream.ToArray();
			}
		}

		private static byte[] DecompressData(byte[] data)
		{
			using (MemoryStream memory_stream = new MemoryStream(data))
			{
				using (DeflateStream deflate_stream = new DeflateStream(memory_stream, CompressionMode.Decompress, true))
				{
					using (MemoryStream memory_reader = new MemoryStream())
					{
						byte[] buffer = new byte[1024];

						try
						{
							while (true)
							{
								int count = deflate_stream.Read(buffer, 0, buffer.Length);

								if (count <= 0) { break; }

								memory_reader.Write(buffer, 0, count);
							}

							return memory_reader.ToArray();
						}
						catch { return null; }
					}
				}
			}
		}
		#endregion
	}
}
